using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace ChEngine
{
    public class PGNParser
    {
        public PGNParser(BitBoardDriver driver)
        {
            this.driver = driver;
        }
       
       

        BitBoardDriver driver;
        
        Regex moveSplit = new Regex(@"[1-9]+[0-9]?\.(?'move'\s*?(O-O|O-O-O|\w+)(=[QNRB])?[+#]?(\s+(O-O|O-O-O|\w+)(=[QNRB])?[+#]?\s+)?)", RegexOptions.Singleline | RegexOptions.Compiled);
        Regex singleMoveSplit = new Regex("(?'piece'[NBRKQ])?((?'drank'[12345678])|(?'dfile'[abcdefgh]))?x?(?'dest'[abcdefgh][12345678])(=(?'promote'[QNRB]))?");
        public ICollection<Movement> ParseMoveText(string s)
        {
            List<Movement> mlist = new List<Movement>();
            s = s.Replace("\r", " ");
            s = s.Replace("\n", " ");
            s = s.Trim() + " ";
            
           
            MatchCollection matches = moveSplit.Matches(s);
            int nCount = 0;
            foreach (Match m in matches)
            {
                ParseSingleMove(m.Groups["move"].Value,++nCount,mlist );
            }
            return mlist;
        }
        private void ThrowSyntaxError(string move,int nCount)
        {
            throw new Exception(string.Format("Syntax Error in {0}. Offending move was:{1}",nCount,move));
        }
        private void ParseSingleMove(string p,int count,ICollection<Movement> coll)
        {
            string[] tokens = p.Split(' ');
            if (tokens.Length < 1)
            {
                ThrowSyntaxError(p, count);  
            }
            if( tokens.Length > 0 )
                coll.Add(ParseSingleMove(tokens[0].Trim(), count, Side.White));
            if (tokens.Length > 1)
                coll.Add(ParseSingleMove(tokens[1].Trim(), count, Side.Black));
            else
                coll.Add(new Movement(0,0,MoveFlag.Nothing));

        }
        public Movement ParseSingleMove(string m, Side s)
        {
            return ParseSingleMove(m, 0, s);
        }
        private Movement ParseSingleMove(string p, int count, Side side)
        {
            Movement move = null;
            if (p == "O-O")
            {
                if (side == Side.Black)
                {
                    move = new Movement(4, 6, MoveFlag.Nothing);
                }
                else
                {
                    move = new Movement(60,62, MoveFlag.Nothing);
                }
            }else
            if (p == "O-O-O")
            {
                if (side == Side.Black)
                {
                    move = new Movement(4, 2, MoveFlag.Nothing);
                }
                else
                {
                    move = new Movement(60, 58, MoveFlag.Nothing);
                }
            }
            else
            {
                Match m = singleMoveSplit.Match(p);
                if (m.Success == false)
                    ThrowSyntaxError(p, count);
                string moving = "P";
                if (!string.IsNullOrEmpty(m.Groups["piece"].Value))
                    moving = m.Groups["piece"].Value;
                move = BuildMove(side,moving, m.Groups["drank"].Value, m.Groups["dfile"].Value, m.Groups["dest"].Value, m.Groups["promote"].Value,p);
            }
            if (null != move)
            {
                driver.RealizeMove(move.ToBitMove());
            }
            return move;
        }

        private Movement BuildMove(Side color,string moving, string drank, string dfile, string dest, string promote,string literal)
        {
            MoveFlag flag = MoveFlag.Nothing;
            int destCell = BitBoardDriver.ToCell(dest);
            ulong destBit = BitBoardDriver.CellToBit(destCell);
            ulong mask = 0xFFFFFFFFFFFFFFFFUL;
            string sfile = "abcdefgh";
            
            if (!string.IsNullOrEmpty(drank))
            {
                ulong rank = 0;
                switch (drank)
                {
                    case "1":
                        rank = BoardRank.Rank1;
                        break;
                    case "2":
                        rank = BoardRank.Rank2;
                        break;
                    case "3":
                        rank = BoardRank.Rank3;
                        break;
                    case "4":
                        rank = BoardRank.Rank4;
                        break;
                    case "5":
                        rank = BoardRank.Rank5;
                        break;
                    case "6":
                        rank = BoardRank.Rank6;
                        break;
                    case "7":
                        rank = BoardRank.Rank7;
                        break;
                    case "8":
                        rank = BoardRank.Rank8;
                        break;

                }
                mask &= rank;
            }
            if (!string.IsNullOrEmpty(dfile))
            {
                ulong file = 0;
                int iFile = sfile.IndexOf(dfile);
                switch (dfile)
                {
                    case "a":
                        file = BoardFile.FileA;
                        break;
                    case "b":
                        file = BoardFile.FileB;
                        break;
                    case "c":
                        file = BoardFile.FileC;
                        break;
                    case "d":
                        file = BoardFile.FileD;
                        break;
                    case "e":
                        file = BoardFile.FileE;
                        break;
                    case "f":
                        file = BoardFile.FileF;
                        break;
                    case "g":
                        file = BoardFile.FileG;
                        break;
                    case "h":
                        file = BoardFile.FileH;
                        break;
                }
                
                mask &= (file|BitBoardDriver.CellToBit(iFile));
            }
            List<BitMove> mbl = new List<BitMove>();
            int[] movesarray = new int[200];
            int nMoves = driver.MoveGenerator.GetMoves(driver.Board,movesarray,0, MoveGenerationMode.All);
            for( int i=0;i<nMoves;++i )
            {
                BitMove bm = BitMove.FromRaw(movesarray[i],driver.Board);

                if ( 0 != (bm.Flag & MoveFlag.EnPassant))
                    continue;
                if ((bm.Current & destBit) != 0) // move can reach the destination cell...
                {
                    if ((bm.Previous & mask) != 0) // remove ambiguity
                    {
                        switch (moving)
                        {
                            case "P":
                                if (0 != (bm.Previous & driver.GetPawns(color)))
                                {
                                    mbl.Add(bm);
                                }
                                break;
                            case "N":
                                if (0 != (bm.Previous & driver.GetKnights(color)))
                                {
                                    mbl.Add(bm);
                                }
                                break;
                            case "B":
                                if (0 != (bm.Previous & driver.GetBishops(color)))
                                {
                                    mbl.Add(bm);
                                }
                                break;
                            case "R":
                                if (0 != (bm.Previous & driver.GetRooks(color)))
                                {
                                    mbl.Add(bm);
                                }
                                break;
                            case "Q":
                                if (0 != (bm.Previous & driver.GetQueens(color)))
                                {
                                    mbl.Add(bm);
                                }
                                break;
                            case "K":
                                if (0 != (bm.Previous & driver.GetKing(color)))
                                {
                                    mbl.Add(bm);
                                }
                                break;

                        }
                    }
                }
            }
            if (mbl.Count > 1)
            {
                throw new Exception("Ambiguous move");
            }
            if (mbl.Count == 0)
            {
                StringBuilder sb = new StringBuilder();
                sb.Append("Invalid move:"+literal+"\r\n");
                sb.Append(string.Format("wp:{0:X}\r\n", driver.GetPawns(Side.White)));
                sb.Append(string.Format("wn:{0:X}\r\n", driver.GetKnights(Side.White)));
                sb.Append(string.Format("wb:{0:X}\r\n", driver.GetBishops(Side.White)));
                sb.Append(string.Format("wr:{0:X}\r\n", driver.GetRooks(Side.White)));
                sb.Append(string.Format("wq:{0:X}\r\n", driver.GetQueens(Side.White)));
                sb.Append(string.Format("wk:{0:X}\r\n", driver.GetKing(Side.White)));
                sb.Append(string.Format("bp:{0:X}\r\n", driver.GetPawns(Side.Black)));
                sb.Append(string.Format("bn:{0:X}\r\n", driver.GetKnights(Side.Black)));
                sb.Append(string.Format("bb:{0:X}\r\n", driver.GetBishops(Side.Black)));
                sb.Append(string.Format("br:{0:X}\r\n", driver.GetRooks(Side.Black)));
                sb.Append(string.Format("bq:{0:X}\r\n", driver.GetQueens(Side.Black)));
                sb.Append(string.Format("bk:{0:X}\r\n", driver.GetKing(Side.Black)));
                throw new Exception(sb.ToString());

            }
            BitMove res = mbl[0];
            ///// TOCHANGE
            if (!string.IsNullOrEmpty(promote))
            {
                switch (promote)
                {
                    case "Q":
                        flag |= MoveFlag.PromoteToQueen;
                        break;
                    case "R":
                        flag |= MoveFlag.PromoteToRook;
                        break;
                    case "B":
                        flag |= MoveFlag.PromoteToBishop;
                        break;
                    case "N":
                        flag |= MoveFlag.PromoteToKnight;
                        break;
                }
            }
            return new Movement(BitBoardDriver.BitToCell(res.Previous),BitBoardDriver.BitToCell(res.Current),flag);
        }
        
    }
}
